/*
 * testfci.c
 *
 * Demonstrates how to use the FCI library APIs
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <dos.h>

#include "..\..\include\fci.h"


/*
 * When a CAB file reaches this size, a new CAB will be created
 * automatically.  This is useful for fitting CAB files onto disks.
 *
 * If you want to create just one huge CAB file with everything in
 * it, change this to a very very large number.
 */
#define MEDIA_SIZE			300000

/*
 * When a folder has this much compressed data inside it,
 * automatically flush the folder.
 *
 * Flushing the folder hurts compression a little bit, but
 * helps random access significantly.
 */
#define FOLDER_THRESHOLD	900000


/*
 * Compression type to use
 */

#define COMPRESSION_TYPE    tcompTYPE_MSZIP


/*
 * Our internal state
 *
 * The FCI APIs allow us to pass back a state pointer of our own
 */
typedef struct
{
    long    total_compressed_size;      /* total compressed size so far */
	long	total_uncompressed_size;	/* total uncompressed size so far */
} client_state;


/*
 * Function prototypes 
 */
void    main(int argc, char **argv);
void    store_cab_name(char *cabname, int iCab);
void    set_cab_parameters(PCCAB cab_parms);
BOOL	test_fci(int num_files, char *file_list[]);
void    strip_path(char *filename, char *stripped_name);
int		get_percentage(unsigned long a, unsigned long b);
char    *return_fci_error_string(FCIERROR err);


void main(int argc, char **argv)
{
	if (argc < 2)
	{
		printf(
			"TESTFCI - Demonstrates how to use the FCI library API calls\n"
			"\n"
			"Usage: TESTFCI file1 [+] [file2] [+] [file3] ...\n"
			"\n"       
			"Where <file1>...<fileN> are input files, and a + forces a folder boundary)\n"
			"Output cabinets are named C:\\TEST1.CAB, C:\\TEST2.CAB, ...\n"
			"\n"
			"  e.g. testfci test.dat source.c test.c + baloon.bmp cabbage.bmp\n"
			"\n"
		);

		exit(0);
	}

	if (test_fci(argc-1, &argv[1]) == TRUE)
		printf("TESTFCI was successful, cabinets created on c:\\\n");
	else
		printf("TESTFCI failed\n");

	exit(0);
}


/*
 * Memory allocation function
 */
FNFCIALLOC(mem_alloc)
{
	return malloc(cb);
}


/*
 * Memory free function
 */
FNFCIFREE(mem_free)
{
	free(memory);
}


/*
 * File i/o functions
 */
FNFCIOPEN(fci_open)
{
    int result;

    result = _open(pszFile, oflag, pmode);

    if (result == -1)
        *err = errno;

    return result;
}

FNFCIREAD(fci_read)
{
    unsigned int result;

    result = (unsigned int) _read(hf, memory, cb);

    if (result != cb)
        *err = errno;

    return result;
}

FNFCIWRITE(fci_write)
{
    unsigned int result;

    result = (unsigned int) _write(hf, memory, cb);

    if (result != cb)
        *err = errno;

    return result;
}

FNFCICLOSE(fci_close)
{
    int result;

    result = _close(hf);

    if (result != 0)
        *err = errno;

    return result;
}

FNFCISEEK(fci_seek)
{
    long result;

    result = _lseek(hf, dist, seektype);

    if (result == -1)
        *err = errno;

    return result;
}

FNFCIDELETE(fci_delete)
{
    int result;

    result = remove(pszFile);

    if (result != 0)
        *err = errno;

    return result;
}


/*
 * File placed function called when a file has been committed
 * to a cabinet
 */
FNFCIFILEPLACED(file_placed)
{
	printf(
		"   placed file '%s' (size %d) on cabinet '%s'\n",
		pszFile, 
		cbFile, 
		pccab->szCab
	);

	if (fContinuation)
		printf("      (Above file is a later segment of a continued file)\n");

	return 0;
}


/*
 * Function to obtain temporary files
 */
FNFCIGETTEMPFILE(get_temp_file)
{
    char    *psz;

    psz = _tempnam("","xx");            // Get a name
    if ((psz != NULL) && (strlen(psz) < (unsigned)cbTempName)) {
        strcpy(pszTempName,psz);        // Copy to caller's buffer
        free(psz);                      // Free temporary name buffer
        return TRUE;                    // Success
    }
    //** Failed
    if (psz) {
        free(psz);
    }

    return FALSE;
}


/*
 * Progress function
 */
FNFCISTATUS(progress)
{
	client_state	*cs;

	cs = (client_state *) pv;

	if (typeStatus == statusFile)
	{
        cs->total_compressed_size += cb1;
		cs->total_uncompressed_size += cb2;

		/*
		 * Compressing a block into a folder
		 *
		 * cb2 = uncompressed size of block
		 */
		printf(
            "Compressing: %9ld -> %9ld             \r",
            cs->total_uncompressed_size,
            cs->total_compressed_size
		);
		
		fflush(stdout);
	}
	else if (typeStatus == statusFolder)
	{
		int	percentage;

		/*
		 * Adding a folder to a cabinet
		 *
		 * cb1 = amount of folder copied to cabinet so far
		 * cb2 = total size of folder
		 */
		percentage = get_percentage(cb1, cb2);

		printf("Copying folder to cabinet: %d%%      \r", percentage);
		fflush(stdout);
	}

	return 0;
}


void store_cab_name(char *cabname, int iCab)
{
	sprintf(cabname, "TEST%d.CAB", iCab);
}


FNFCIGETNEXTCABINET(get_next_cabinet)
{
	/*
	 * Cabinet counter has been incremented already by FCI
	 */

	/*
	 * Store next cabinet name
	 */
	store_cab_name(pccab->szCab, pccab->iCab);

	/*
	 * You could change the disk name here too, if you wanted
	 */

	return TRUE;
}


FNFCIGETOPENINFO(get_open_info)
{
	BY_HANDLE_FILE_INFORMATION	finfo;
	FILETIME					filetime;
	HANDLE						handle;
    DWORD                       attrs;
    int                         hf;

    /*
     * Need a Win32 type handle to get file date/time
     * using the Win32 APIs, even though the handle we
     * will be returning is of the type compatible with
     * _open
     */
	handle = CreateFile(
		pszName,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
		NULL
	);
   
	if (handle == INVALID_HANDLE_VALUE)
	{
		return -1;
	}

	if (GetFileInformationByHandle(handle, &finfo) == FALSE)
	{
		CloseHandle(handle);
		return -1;
	}
   
	FileTimeToLocalFileTime(
		&finfo.ftLastWriteTime, 
		&filetime
	);

	FileTimeToDosDateTime(
		&filetime,
		pdate,
		ptime
	);

    attrs = GetFileAttributes(pszName);

    if (attrs == 0xFFFFFFFF)
    {
        /* failure */
        *pattribs = 0;
    }
    else
    {
        /*
         * Mask out all other bits except these four, since other
         * bits are used by the cabinet format to indicate a
         * special meaning.
         */
        *pattribs = (int) (attrs & (_A_RDONLY | _A_SYSTEM | _A_HIDDEN | _A_ARCH));
    }

    CloseHandle(handle);


    /*
     * Return handle using _open
     */
	hf = _open( pszName, _O_RDONLY | _O_BINARY );

	if (hf == -1)
		return -1; // abort on error
   
	return hf;
}


void set_cab_parameters(PCCAB cab_parms)
{
	memset(cab_parms, 0, sizeof(CCAB));

	cab_parms->cb = MEDIA_SIZE;
	cab_parms->cbFolderThresh = FOLDER_THRESHOLD;

	/*
	 * Don't reserve space for any extensions
	 */
	cab_parms->cbReserveCFHeader = 0;
	cab_parms->cbReserveCFFolder = 0;
	cab_parms->cbReserveCFData   = 0;

	/*
	 * We use this to create the cabinet name
	 */
	cab_parms->iCab = 1;

	/*
	 * If you want to use disk names, use this to
	 * count disks
	 */
	cab_parms->iDisk = 0;

	/*
	 * Choose your own number
	 */
	cab_parms->setID = 12345;

	/*
	 * Only important if CABs are spanning multiple
	 * disks, in which case you will want to use a
	 * real disk name.
	 *
	 * Can be left as an empty string.
	 */
	strcpy(cab_parms->szDisk, "MyDisk");

	/* where to store the created CAB files */
	strcpy(cab_parms->szCabPath, "c:\\");

	/* store name of first CAB file */
	store_cab_name(cab_parms->szCab, cab_parms->iCab);
}


BOOL test_fci(int num_files, char *file_list[])
{
	HFCI			hfci;
	ERF				erf;
	CCAB			cab_parameters;
	int				i;
	client_state	cs;

	/*  
	 * Initialise our internal state
	 */
    cs.total_compressed_size = 0;
	cs.total_uncompressed_size = 0;

	set_cab_parameters(&cab_parameters);

	hfci = FCICreate(
		&erf,
		file_placed,
		mem_alloc,
		mem_free,
        fci_open,
        fci_read,
        fci_write,
        fci_close,
        fci_seek,
        fci_delete,
		get_temp_file,
        &cab_parameters,
        &cs
	);

	if (hfci == NULL)
	{
		printf("FCICreate() failed: code %d [%s]\n",
			erf.erfOper, return_fci_error_string(erf.erfOper)
		);

		return FALSE;
	}

	for (i = 0; i < num_files; i++)
	{
		char	stripped_name[256];

		/*
		 * Flush the folder?
		 */
		if (!strcmp(file_list[i], "+"))
		{
			if (FALSE == FCIFlushFolder(
				hfci,
				get_next_cabinet,
				progress))
			{
				printf("FCIFlushFolder() failed: code %d [%s]\n",
					erf.erfOper, return_fci_error_string(erf.erfOper)
				);

                (void) FCIDestroy(hfci);

				return FALSE;
			}

			continue;
		}

		/*
		 * Don't store the path name in the cabinet file!
		 */
		strip_path(file_list[i], stripped_name);

		if (FALSE == FCIAddFile(
			hfci,
			file_list[i],  /* file to add */
			stripped_name, /* file name in cabinet file */
			FALSE, /* file is not executable */
			get_next_cabinet,
			progress,
			get_open_info,
            COMPRESSION_TYPE))
		{
			printf("FCIAddFile() failed: code %d [%s]\n",
				erf.erfOper, return_fci_error_string(erf.erfOper)
			);

            (void) FCIDestroy(hfci);

			return FALSE;
		}
	}		

	/*
	 * This will automatically flush the folder first
	 */
	if (FALSE == FCIFlushCabinet(
		hfci,
		FALSE,
		get_next_cabinet,
		progress))
	{
		printf("FCIFlushCabinet() failed: code %d [%s]\n",
			erf.erfOper, return_fci_error_string(erf.erfOper)
		);

        (void) FCIDestroy(hfci);

		return FALSE;
	}

    if (FCIDestroy(hfci) != TRUE)
	{
		printf("FCIDestroy() failed: code %d [%s]\n",
			erf.erfOper, return_fci_error_string(erf.erfOper)
		);

		return FALSE;
	}

	printf("                                                 \r");

	return TRUE;
}


void strip_path(char *filename, char *stripped_name)
{
	char	*p;

	p = strrchr(filename, '\\');

	if (p == NULL)
		strcpy(stripped_name, filename);
	else
		strcpy(stripped_name, p+1);
}


int get_percentage(unsigned long a, unsigned long b)
{
	while (a > 10000000)
	{
		a >>= 3;
		b >>= 3;
	}

	if (b == 0)
		return 0;

	return ((a*100)/b);
}


char *return_fci_error_string(FCIERROR err)
{
	switch (err)
	{
		case FCIERR_NONE:
			return "No error";

		case FCIERR_OPEN_SRC:
			return "Failure opening file to be stored in cabinet";
		
		case FCIERR_READ_SRC:
			return "Failure reading file to be stored in cabinet";
		
		case FCIERR_ALLOC_FAIL:
			return "Insufficient memory in FCI";

		case FCIERR_TEMP_FILE:
			return "Could not create a temporary file";

		case FCIERR_BAD_COMPR_TYPE:
			return "Unknown compression type";

		case FCIERR_CAB_FILE:
			return "Could not create cabinet file";

		case FCIERR_USER_ABORT:
			return "Client requested abort";

		case FCIERR_MCI_FAIL:
			return "Failure compressing data";

		default:
			return "Unknown error";
	}
}
